You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Fix rewriting of configured async enumeration and disposal - #527
Fix a process-crashing rewrite of configured async enumeration on .NET 8, including await foreach (var item in source.WithCancellation(token)) and source.ConfigureAwait(...). Also handle configured async disposal, which shares
the same configured-ValueTask producer boundary.
Rebased onto upstream main. The constructor prerequisite, #529,
merged on October 23, 2026 and is no longer part of this PR's diff. All integration
regressions previously added in the merge commit are preserved.
The rewriter already replaces ConfiguredValueTaskAwaitable and its awaiters,
but did not replace the framework's configured enumerable, enumerator, or
disposable. Their methods therefore produced native structs for consumers
rewritten to expect Coyote's different struct layout.
Changes
Map TaskAsyncEnumerableExtensions, ConfiguredCancelableAsyncEnumerable<T>,
its nested enumerator, and ConfiguredAsyncDisposable to Coyote wrappers.
Route MoveNextAsync and DisposeAsync through the existing controllable
configured-ValueTask awaitables.
Preserve cancellation-token replacement and configuration chaining. Existing
awaiter behavior continues to force context capture during controlled testing.
Rewrite static and bound ldftn/ldvirtftn method-group targets along with
their delegate types, preserving instance binding instead of using static adapters.
Visit all bodyless signatures in the member pass, including custom delegates,
abstract methods, and interface methods, rather than skipping them with
instruction-pass traversal.
Rewrite type operands consistently, including boxing/unboxing and array
allocation, stores, loads, and element addresses. Preserve rewritten caller
type arguments when invoking external generic methods.
Rewrite base-type and implemented-interface references consistently with
method signatures. Keep base-constructor calls as instance calls when their
generic arguments are remapped.
Rewrite closed-generic explicit-interface override declarations and both
method and type generic constraints. Preserve instance call opcodes, including
nonvirtual base dispatch and callvirt on nonvirtual instance getters.
Rewrite field-reference declaring types alongside field types for all field
operands, including instance/static loads, stores, and addresses.
Rewrite synthetic rectangular-array constructors and accessors without
resolving nonexistent method definitions. Remap caller-supplied arguments on
external generic declaring types as well as generic methods, preserving
external concrete signatures and instance/base-constructor binding.
Rewrite method and constructor ldtoken operands while preserving their opcode
and instance binding, including expression-tree construction and compilation.
Remap property, indexer and event metadata alongside accessor signatures.
Keep generic method tokens consistent with their paired type handles by
preserving native declaring types for static-adapter-only replacements.
Forward ToBlockingEnumerable<T> to its native implementation under NET7_0_OR_GREATER, preserving mapped generic arguments and cancellation.
Include 53 configured regression/control cases covering synchronous/asynchronous
iteration and disposal, context-capture settings, cancellation, chained
configuration, early-exit cleanup, failure replay, generic constructors
(including the nested enumerator), generic/custom enumeration delegates, and
non-generic disposal delegates, bound and virtual method groups, boxed round
trips, arrays of all three configured structs, an instance-adapter control,
generic inheritance, abstract/explicit-interface parameter and return types,
closed-generic explicit implementations, virtual base calls, generic constraints,
tuple fields, generic static fields, native null-source exception parity,
rectangular arrays, unrewritten generic containers and base classes,
method/constructor expression tokens, native instance-method binding, and
blocking enumeration of configured structs and previously mapped awaiters,
property/indexer expressions, event metadata/dispatch, and native generic
static-member expression handles with both native and mapped type arguments.
Guard the configured APIs with NET, leaving the netstandard2.0 API unchanged.
Update the inspected BugFinding and Rewriting IL-diff baselines. The other three
hashes remain unchanged.
This does not address CancellationTokenSource.CancelAsync interception or TPL
Dataflow scheduling support. ToBlockingEnumerable<T> retains native blocking
behavior; the forwarding overload fixes type compatibility, not scheduler semantics.
Validation
Windows, pinned .NET SDK 8.0.425:
Complete Release solution rebuild, including netstandard2.0: 0 warnings,
0 errors.
All six CI test suites (runtime, rewriting, testing, actors, actors-testing, tools): 1,398 passed, 0 failed, 0 skipped.
ILVerify 8.0.0, matching CI: all classes and methods verified in all six
CI test assemblies.
Tests/compare-rewriting-diff-logs.ps1: all five hashes match.
git diff --check: clean.
Before the delegate fix, local ILVerify reproduced the previous CI run's DelegateCtor and StackUnexpected failures in TestConfigurationMethodGroup and TestCustomConfigurationMethodGroup.
After the fix, the complete BugFinding assembly verifies.
The six initial follow-up review reproductions exposed another 28 ILVerify
errors in bound delegates, boxing/unboxing, and array operations. Those are
fixed; two additional regressions verify nonvirtual struct binding and preserve
native instance binding for methods normally rewritten through static adapters.
Further inheritance/signature regressions reproduced six ILVerify errors
before the metadata correction. Verification also caught the base-constructor
call mismatch even while the runtime cases passed; the final fix verifies cleanly.
The dispatch/constraint regressions reproduced four runtime failures and five ILVerify
errors for explicit-interface overrides, recursive virtual base dispatch, and
type/method constraints. All four now pass, and all six assemblies verify.
The tuple/static-field regressions passed at runtime but reproduced 11 ILVerify errors before the field-reference correction; both now verify.
The null-source review claim was checked against the native CoreLib APIs by
reflection, bypassing rewriting: configuration accepts null, and enumeration
throws NullReferenceException. Three controls prove the wrappers preserve
that behavior; no incompatible immediate null guards were added.
Three array/external-container regressions reproduced 29 ILVerify errors, despite passing at
runtime, for rectangular-array constructors/accessors and unrewritten generic
containers/base classes. The final references verify, including a generic
method on a generic external container; all six assemblies verify cleanly.
The expression-token and blocking-enumeration cases reproduced five
runtime failures and four ILVerify errors before correction. Six cases now
pass, including a native instance-binding control, cancellation-token propagation,
and a previously mapped TaskAwaiter<int> element type.
The latest property/indexer and generic static-member expression regressions
reproduced four runtime failures before correction. All four now construct,
compile and execute successfully; an event metadata/dispatch control also passes.
Native factory identity is checked through reflection so the control does not
compare against a separately rewritten direct getter.
The six suites were invoked explicitly with dotnet vstest to avoid treating
the non-test Tests.Common helper assembly as a seventh test project.
GitHub Actions run 40399749907
passed on aa1a6f58: Coyote build/tests and sample build/tests on Windows,
Ubuntu, and macOS, plus CLA. All fifteen confirmed findings have verified
reproductions and fixes, and all eleven inline threads are resolved.
The latest automated review reports no open issues and recommends final human
review of the shared IL and metadata changes. The null-source false positive is
documented with native parity controls. Maintainer approval and merge remain.
Initialize the shared type visitor state before processing bodyless delegate signatures. Fresh rebuild, focused regressions, ILVerify and all five IL-diff hashes remain clean.
Copilot-Session: 240e36d3-c67f-4128-b182-3c10c4ea6879
This PR fixes configured async enumeration and disposal rewriting by routing framework configuration APIs through Coyote wrappers and its controllable ValueTask awaitables.
Changes:
Add mappings and wrappers for configured enumerables, enumerators, and async disposables under NET.
Extend rewriting to static method-group targets and custom delegate signatures.
Add regression coverage and update the BugFinding IL-diff baseline.
Frank the Clank says: Pantazis Deligiannis (@pdeligia), this is rebased onto current main, with the already-merged constructor fix from #529 removed from the diff and its configured integration regressions preserved.
The previous CI failures were genuine delegate IL issues: static ldftn targets and bodyless custom-delegate signatures were not rewritten consistently. Both are corrected, with additional disposal-method-group coverage and an inspected IL baseline.
All checks on head 6c9cd31c966d1283a204d07ff9990dc1303f831a are green: Coyote and samples on Windows, Ubuntu, and macOS, plus CLA (run 40372631644). Local validation also passed all 1,364 CI-suite tests, ILVerify 8.0.0 for all six test assemblies, and all five IL-diff hashes.
Ready for maintainer review and merge. GitHub denied the author's formal reviewer request, so this comment is the review handoff.
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
Array allocation and access operands remain inconsistent with the newly mapped configured structs, producing invalid IL when those structs are stored in arrays.
…ntly
Remap all type operands, including boxing, unboxing and array access. Rewrite ldftn and ldvirtftn references without replacing instance binding with static adapters. Preserve rewritten caller generic arguments on external generic methods. Add eight regressions for bound methods, adapter controls, boxed round trips and arrays; refresh the inspected BugFinding IL baseline. All 1,372 tests and six ILVerify assemblies pass.
Copilot-Session: 240e36d3-c67f-4128-b182-3c10c4ea6879
Rewrite base and implemented-interface metadata. Visit all bodyless signatures during the member pass instead of special-casing delegates. Preserve instance targets for remapped base-constructor calls. Cover generic collection inheritance, explicit interface implementations, and abstract/interface parameter and return signatures. All 1,375 tests, six ILVerify assemblies and five hashes pass.
Copilot-Session: 240e36d3-c67f-4128-b182-3c10c4ea6879
Rewrite explicit interface declarations and generic constraints alongside method signatures. Preserve instance call opcodes so virtual base calls do not recurse and generic getters retain their original dispatch semantics. Add four regressions and refresh inspected IL baselines.
Copilot-Session: 240e36d3-c67f-4128-b182-3c10c4ea6879
The enumerable ConfigureAwait and WithCancellation replacements accept a null source, whereas the framework APIs immediately throw ArgumentNullException("source"). Rewritten calls therefore succeed until enumeration throws NullReferenceException, changing exception handling during testing. Add immediate null-source checks to both methods (also at line 37).
Remap field declaring types as well as field types for all FieldReference operands. Cover tuple loads, stores and addresses plus generic static field access, reproducing eleven ILVerify errors before correction. Add native CoreLib controls confirming deferred null-enumerable exceptions without changing wrapper behavior.
Copilot-Session: 240e36d3-c67f-4128-b182-3c10c4ea6879
Frank the Clank says: The latest review overview also requests immediate ArgumentNullException("source") guards for enumerable ConfigureAwait and WithCancellation. That premise is incorrect for .NET 8: the framework extensions and configured enumerable constructor accept null, and GetAsyncEnumerator later throws NullReferenceException. In a6f6d98, TestNullEnumerableConfiguration checks all three configurations against the installed native CoreLib APIs via reflection, bypassing rewriting, then verifies identical rewritten behavior. All three controls pass. No immediate guards were added because they would change framework behavior. The separate generic-field issue is fixed with two regressions reproducing 11 ILVerify errors before the correction; all 1,384 local tests and all six verified assemblies now pass. New cross-platform CI is running.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Shared IL and metadata changes require human review, with unresolved method-reference mismatches affecting external generic types and rectangular arrays.
No open issues 🎉
2 previously missed issues
In code that hasn't changed since last review
Handle rectangular array constructor and accessor rewriting
Rectangular arrays such as new ConfiguredAsyncDisposable[1, 1] use synthetic constructor and accessor method references rather than the type operands handled here. Normal method rewriting cannot resolve those array methods, leaving framework-typed operands incompatible with rewritten locals and values. Add a dedicated array-method rewriting path that bypasses method-definition resolution for both allocation and element access, with a rectangular-array store/load regression.
Rewrite generic arguments on external types and base calls
For Box<T> in an unrewritten, non-System assembly, new Box<ConfiguredAsyncDisposable>(resource.ConfigureAwait(false)) retains the framework-typed constructor while its argument and local use Coyote's struct, producing incompatible IL. The same omission leaves base-constructor calls inconsistent with the remapped base type in MemberTypeRewritingPass.cs:33. Rewrite caller-supplied generic arguments on external declaring types as well as generic methods, preserving external concrete signatures, and add external generic-container and base-class regressions.
Rebuild synthetic array constructor/accessor references without resolving nonexistent method definitions. Remap caller-supplied arguments on external declaring types and generic methods without changing native concrete signatures. Add three regressions with an unrewritten generic helper, reproducing 29 ILVerify errors before correction, and refresh the inspected baseline.
Copilot-Session: 240e36d3-c67f-4128-b182-3c10c4ea6879
Frank the Clank says: Both additional findings in the latest review overview are reproduced and fixed in 9b4f1cd. TestConfiguredRectangularArrays covers allocation, Get, Set and Address for all three configured struct types; the rewriter now rebuilds synthetic array method references without definition lookup and preserves their opcodes. TestExternalConfiguredGenericContainer and TestExternalConfiguredGenericBase use an unrewritten Tests.Common helper to cover constructor arguments, getters, a generic method on a generic declaring type, inheritance and base-constructor calls. External caller-supplied type arguments are remapped while native concrete signatures remain untouched. All three cases passed at runtime before correction but exposed 29 ILVerify errors. After correction all 1,387 tests, six ILVerify assemblies and five inspected hashes pass, with zero build warnings/errors. Cross-platform CI for the new head is running.
Method-reference ldtoken operands are still skipped: Expression<Func<ConfiguredAsyncDisposable>> expression = () => source.ConfigureAwait(false) retains a token for the framework's ConfigureAwait. The rewritten delegate returns Coyote's struct while the expression-tree call returns the framework struct, so constructing the lambda throws ArgumentException. Remap method and constructor tokens while preserving their opcode and instance binding, and add an expression-tree regression.
Preserve ToBlockingEnumerable mapping for rewritten awaitable types
Mapping this class also redirects lookup of .NET 8's ToBlockingEnumerable<T>, but the replacement has no matching overload, so TryFindMethod fails before rewriting generic arguments. Calls with T = ConfiguredAsyncDisposable therefore retain the framework struct while rewritten producers and consumers use Coyote's struct, producing incompatible IL; already-mapped awaiter types are affected too. Add a framework-guarded forwarding overload, or retain the native target while rewriting its generic arguments, and add an IL-verification regression.
Remap method and constructor ldtoken operands while retaining opcodes and instance binding. Add a NET7_0_OR_GREATER native ToBlockingEnumerable forwarder so mapped generic arguments remain representation-consistent. Cover expression construction/compilation, native instance binding, cancellation propagation and previously mapped awaiters; reproduce five runtime failures and four ILVerify errors before correction.
Copilot-Session: 240e36d3-c67f-4128-b182-3c10c4ea6879
Frank the Clank says: Both new review-overview findings are reproduced and fixed in 777edc9. Method/constructor ldtoken operands now use method-reference rewriting while retaining ldtoken and instance binding; expression-tree construction/compilation tests cover configured disposal and a generic constructor, with native Task.Wait as an instance-binding control. The NET7_0_OR_GREATER ToBlockingEnumerable forwarding overload preserves mapped caller type arguments and cancellation while retaining native blocking behavior, not adding scheduler semantics. Blocking cases cover ConfiguredAsyncDisposable and the previously mapped TaskAwaiter, including cancellation-token propagation. Before correction these six cases produced five runtime failures and four ILVerify errors; after correction all 1,393 tests, six ILVerify assemblies and five inspected hashes pass with zero build warnings/errors. Cross-platform CI for the new head is running.
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
Cross-cutting IL and metadata changes require human review, with unresolved metadata and method-handle inconsistencies affecting expression construction.
Remap property, indexer and event metadata with accessor signatures. Preserve native generic declaring types for method tokens whose replacements are static adapters so paired method/type handles remain compatible. Add expression and event controls, and update the inspected IL diff hash.
Copilot-Session: 240e36d3-c67f-4128-b182-3c10c4ea6879
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The cross-cutting changes to shared IL generation and metadata binding warrant final human review despite extensive regression coverage and reported validation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fix a process-crashing rewrite of configured async enumeration on .NET 8, including
await foreach (var item in source.WithCancellation(token))andsource.ConfigureAwait(...). Also handle configured async disposal, which sharesthe same configured-ValueTask producer boundary.
Rebased onto upstream
main. The constructor prerequisite, #529,merged on October 23, 2026 and is no longer part of this PR's diff. All integration
regressions previously added in the merge commit are preserved.
The rewriter already replaces
ConfiguredValueTaskAwaitableand its awaiters,but did not replace the framework's configured enumerable, enumerator, or
disposable. Their methods therefore produced native structs for consumers
rewritten to expect Coyote's different struct layout.
Changes
TaskAsyncEnumerableExtensions,ConfiguredCancelableAsyncEnumerable<T>,its nested enumerator, and
ConfiguredAsyncDisposableto Coyote wrappers.MoveNextAsyncandDisposeAsyncthrough the existing controllableconfigured-ValueTask awaitables.
awaiter behavior continues to force context capture during controlled testing.
ldftn/ldvirtftnmethod-group targets along withtheir delegate types, preserving instance binding instead of using static adapters.
Visit all bodyless signatures in the member pass, including custom delegates,
abstract methods, and interface methods, rather than skipping them with
instruction-pass traversal.
allocation, stores, loads, and element addresses. Preserve rewritten caller
type arguments when invoking external generic methods.
method signatures. Keep base-constructor calls as instance calls when their
generic arguments are remapped.
method and type generic constraints. Preserve instance call opcodes, including
nonvirtual base dispatch and
callvirton nonvirtual instance getters.operands, including instance/static loads, stores, and addresses.
resolving nonexistent method definitions. Remap caller-supplied arguments on
external generic declaring types as well as generic methods, preserving
external concrete signatures and instance/base-constructor binding.
ldtokenoperands while preserving their opcodeand instance binding, including expression-tree construction and compilation.
Keep generic method tokens consistent with their paired type handles by
preserving native declaring types for static-adapter-only replacements.
ToBlockingEnumerable<T>to its native implementation underNET7_0_OR_GREATER, preserving mapped generic arguments and cancellation.iteration and disposal, context-capture settings, cancellation, chained
configuration, early-exit cleanup, failure replay, generic constructors
(including the nested enumerator), generic/custom enumeration delegates, and
non-generic disposal delegates, bound and virtual method groups, boxed round
trips, arrays of all three configured structs, an instance-adapter control,
generic inheritance, abstract/explicit-interface parameter and return types,
closed-generic explicit implementations, virtual base calls, generic constraints,
tuple fields, generic static fields, native null-source exception parity,
rectangular arrays, unrewritten generic containers and base classes,
method/constructor expression tokens, native instance-method binding, and
blocking enumeration of configured structs and previously mapped awaiters,
property/indexer expressions, event metadata/dispatch, and native generic
static-member expression handles with both native and mapped type arguments.
NET, leaving thenetstandard2.0API unchanged.hashes remain unchanged.
This does not address
CancellationTokenSource.CancelAsyncinterception or TPLDataflow scheduling support.
ToBlockingEnumerable<T>retains native blockingbehavior; the forwarding overload fixes type compatibility, not scheduler semantics.
Validation
Windows, pinned .NET SDK 8.0.425:
netstandard2.0: 0 warnings,0 errors.
runtime,rewriting,testing,actors,actors-testing,tools): 1,398 passed, 0 failed, 0 skipped.CI test assemblies.
Tests/compare-rewriting-diff-logs.ps1: all five hashes match.git diff --check: clean.Before the delegate fix, local ILVerify reproduced the previous CI run's
DelegateCtorandStackUnexpectedfailures inTestConfigurationMethodGroupandTestCustomConfigurationMethodGroup.After the fix, the complete BugFinding assembly verifies.
The six initial follow-up review reproductions exposed another 28 ILVerify
errors in bound delegates, boxing/unboxing, and array operations. Those are
fixed; two additional regressions verify nonvirtual struct binding and preserve
native instance binding for methods normally rewritten through static adapters.
Further inheritance/signature regressions reproduced six ILVerify errors
before the metadata correction. Verification also caught the base-constructor
call mismatch even while the runtime cases passed; the final fix verifies cleanly.
The dispatch/constraint regressions reproduced four runtime failures and five ILVerify
errors for explicit-interface overrides, recursive virtual base dispatch, and
type/method constraints. All four now pass, and all six assemblies verify.
The tuple/static-field regressions passed at runtime but reproduced
11 ILVerify errors before the field-reference correction; both now verify.
The null-source review claim was checked against the native CoreLib APIs by
reflection, bypassing rewriting: configuration accepts null, and enumeration
throws
NullReferenceException. Three controls prove the wrappers preservethat behavior; no incompatible immediate null guards were added.
Three array/external-container regressions reproduced 29 ILVerify errors, despite passing at
runtime, for rectangular-array constructors/accessors and unrewritten generic
containers/base classes. The final references verify, including a generic
method on a generic external container; all six assemblies verify cleanly.
The expression-token and blocking-enumeration cases reproduced five
runtime failures and four ILVerify errors before correction. Six cases now
pass, including a native instance-binding control, cancellation-token propagation,
and a previously mapped
TaskAwaiter<int>element type.The latest property/indexer and generic static-member expression regressions
reproduced four runtime failures before correction. All four now construct,
compile and execute successfully; an event metadata/dispatch control also passes.
Native factory identity is checked through reflection so the control does not
compare against a separately rewritten direct getter.
The six suites were invoked explicitly with
dotnet vstestto avoid treatingthe non-test
Tests.Commonhelper assembly as a seventh test project.GitHub Actions run 40399749907
passed on
aa1a6f58: Coyote build/tests and sample build/tests on Windows,Ubuntu, and macOS, plus CLA. All fifteen confirmed findings have verified
reproductions and fixes, and all eleven inline threads are resolved.
The latest automated review reports no open issues and recommends final human
review of the shared IL and metadata changes. The null-source false positive is
documented with native parity controls. Maintainer approval and merge remain.